1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright 2005 The Apache Software Foundation.
7    *
8    *  Licensed under the Apache License, Version 2.0 (the "License");
9    *  you may not use this file except in compliance with the License.
10   *  You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   *  Unless required by applicable law or agreed to in writing, software
15   *  distributed under the License is distributed on an "AS IS" BASIS,
16   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   *  See the License for the specific language governing permissions and
18   *  limitations under the License.
19   *
20   */
21  /*
22   * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
23   */
24  /*
25   * ===========================================================================
26   *
27   * (C) Copyright IBM Corp. 2003 All Rights Reserved.
28   *
29   * ===========================================================================
30   */
31  /*
32   * $Id: DOMReference.java,v 1.2 2008/07/24 15:20:32 mullan Exp $
33   */
34  package org.jcp.xml.dsig.internal.dom;
35  
36  import javax.xml.crypto.*;
37  import javax.xml.crypto.dsig.*;
38  import javax.xml.crypto.dom.DOMCryptoContext;
39  import javax.xml.crypto.dom.DOMURIReference;
40  
41  import java.io.*;
42  import java.net.URI;
43  import java.net.URISyntaxException;
44  import java.security.*;
45  import java.util.*;
46  import java.util.logging.Level;
47  import java.util.logging.Logger;
48  import org.w3c.dom.Attr;
49  import org.w3c.dom.Document;
50  import org.w3c.dom.Element;
51  import org.w3c.dom.Node;
52  
53  import org.jcp.xml.dsig.internal.DigesterOutputStream;
54  import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
55  import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput;
56  import com.sun.org.apache.xml.internal.security.utils.Base64;
57  import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream;
58  
59  /**
60   * DOM-based implementation of Reference.
61   *
62   * @author Sean Mullan
63   * @author Joyce Leung
64   */
65  public final class DOMReference extends DOMStructure
66      implements Reference, DOMURIReference {
67  
68     /**
69      * Look up useC14N11 system property. If true, an explicit C14N11 transform
70      * will be added if necessary when generating the signature. See section
71      * 3.1.1 of http://www.w3.org/2007/xmlsec/Drafts/xmldsig-core/ for more info.
72      *
73      * If true, overrides the same property if set in the XMLSignContext.
74      */
75      private static boolean useC14N11 =
76          AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
77              public Boolean run() {
78                  return Boolean.getBoolean
79                      ("com.sun.org.apache.xml.internal.security.useC14N11");
80              }
81          });
82  
83      private static Logger log = Logger.getLogger("org.jcp.xml.dsig.internal.dom");
84  
85      private final DigestMethod digestMethod;
86      private final String id;
87      private final List transforms;
88      private List allTransforms;
89      private final Data appliedTransformData;
90      private Attr here;
91      private final String uri;
92      private final String type;
93      private byte[] digestValue;
94      private byte[] calcDigestValue;
95      private Element refElem;
96      private boolean digested = false;
97      private boolean validated = false;
98      private boolean validationStatus;
99      private Data derefData;
100     private InputStream dis;
101     private MessageDigest md;
102     private Provider provider;
103 
104     /**
105      * Creates a <code>Reference</code> from the specified parameters.
106      *
107      * @param uri the URI (may be null)
108      * @param type the type (may be null)
109      * @param dm the digest method
110      * @param transforms a list of {@link Transform}s. The list
111      *    is defensively copied to protect against subsequent modification.
112      *    May be <code>null</code> or empty.
113      * @param id the reference ID (may be <code>null</code>)
114      * @return a <code>Reference</code>
115      * @throws NullPointerException if <code>dm</code> is <code>null</code>
116      * @throws ClassCastException if any of the <code>transforms</code> are
117      *    not of type <code>Transform</code>
118      */
119     public DOMReference(String uri, String type, DigestMethod dm,
120         List transforms, String id, Provider provider) {
121         this(uri, type, dm, null, null, transforms, id, null, provider);
122     }
123 
124     public DOMReference(String uri, String type, DigestMethod dm,
125         List appliedTransforms, Data result, List transforms, String id,
126         Provider provider) {
127         this(uri, type, dm, appliedTransforms,
128              result, transforms, id, null, provider);
129     }
130 
131     public DOMReference(String uri, String type, DigestMethod dm,
132         List appliedTransforms, Data result, List transforms, String id,
133         byte[] digestValue, Provider provider) {
134         if (dm == null) {
135             throw new NullPointerException("DigestMethod must be non-null");
136         }
137         this.allTransforms = new ArrayList();
138         if (appliedTransforms != null) {
139             List transformsCopy = new ArrayList(appliedTransforms);
140             for (int i = 0, size = transformsCopy.size(); i < size; i++) {
141                 if (!(transformsCopy.get(i) instanceof Transform)) {
142                     throw new ClassCastException
143                         ("appliedTransforms["+i+"] is not a valid type");
144                 }
145             }
146             this.allTransforms = transformsCopy;
147         }
148         if (transforms == null) {
149             this.transforms = Collections.EMPTY_LIST;
150         } else {
151             List transformsCopy = new ArrayList(transforms);
152             for (int i = 0, size = transformsCopy.size(); i < size; i++) {
153                 if (!(transformsCopy.get(i) instanceof Transform)) {
154                     throw new ClassCastException
155                         ("transforms["+i+"] is not a valid type");
156                 }
157             }
158             this.transforms = transformsCopy;
159             this.allTransforms.addAll(transformsCopy);
160         }
161         this.digestMethod = dm;
162         this.uri = uri;
163         if ((uri != null) && (!uri.equals(""))) {
164             try {
165                 new URI(uri);
166             } catch (URISyntaxException e) {
167                 throw new IllegalArgumentException(e.getMessage());
168             }
169         }
170         this.type = type;
171         this.id = id;
172         if (digestValue != null) {
173             this.digestValue = (byte[]) digestValue.clone();
174             this.digested = true;
175         }
176         this.appliedTransformData = result;
177         this.provider = provider;
178     }
179 
180     /**
181      * Creates a <code>DOMReference</code> from an element.
182      *
183      * @param refElem a Reference element
184      */
185     public DOMReference(Element refElem, XMLCryptoContext context,
186         Provider provider) throws MarshalException {
187         // unmarshal Transforms, if specified
188         Element nextSibling = DOMUtils.getFirstChildElement(refElem);
189         List transforms = new ArrayList(5);
190         if (nextSibling.getLocalName().equals("Transforms")) {
191             Element transformElem = DOMUtils.getFirstChildElement(nextSibling);
192             while (transformElem != null) {
193                 transforms.add
194                     (new DOMTransform(transformElem, context, provider));
195                 transformElem = DOMUtils.getNextSiblingElement(transformElem);
196             }
197             nextSibling = DOMUtils.getNextSiblingElement(nextSibling);
198         }
199 
200         // unmarshal DigestMethod
201         Element dmElem = nextSibling;
202         this.digestMethod = DOMDigestMethod.unmarshal(dmElem);
203 
204         // unmarshal DigestValue
205         try {
206             Element dvElem = DOMUtils.getNextSiblingElement(dmElem);
207             this.digestValue = Base64.decode(dvElem);
208         } catch (Base64DecodingException bde) {
209             throw new MarshalException(bde);
210         }
211 
212         // unmarshal attributes
213         this.uri = DOMUtils.getAttributeValue(refElem, "URI");
214         this.id = DOMUtils.getAttributeValue(refElem, "Id");
215 
216         this.type = DOMUtils.getAttributeValue(refElem, "Type");
217         this.here = refElem.getAttributeNodeNS(null, "URI");
218         this.refElem = refElem;
219         this.transforms = transforms;
220         this.allTransforms = transforms;
221         this.appliedTransformData = null;
222         this.provider = provider;
223     }
224 
225     public DigestMethod getDigestMethod() {
226         return digestMethod;
227     }
228 
229     public String getId() {
230         return id;
231     }
232 
233     public String getURI() {
234         return uri;
235     }
236 
237     public String getType() {
238         return type;
239     }
240 
241     public List getTransforms() {
242         return Collections.unmodifiableList(allTransforms);
243     }
244 
245     public byte[] getDigestValue() {
246         return (digestValue == null ? null : (byte[]) digestValue.clone());
247     }
248 
249     public byte[] getCalculatedDigestValue() {
250         return (calcDigestValue == null ? null
251                 : (byte[]) calcDigestValue.clone());
252     }
253 
254     public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
255         throws MarshalException {
256         if (log.isLoggable(Level.FINE)) {
257             log.log(Level.FINE, "Marshalling Reference");
258         }
259         Document ownerDoc = DOMUtils.getOwnerDocument(parent);
260 
261         refElem = DOMUtils.createElement
262             (ownerDoc, "Reference", XMLSignature.XMLNS, dsPrefix);
263 
264         // set attributes
265         DOMUtils.setAttributeID(refElem, "Id", id);
266         DOMUtils.setAttribute(refElem, "URI", uri);
267         DOMUtils.setAttribute(refElem, "Type", type);
268 
269         // create and append Transforms element
270         if (!allTransforms.isEmpty()) {
271             Element transformsElem = DOMUtils.createElement
272                 (ownerDoc, "Transforms", XMLSignature.XMLNS, dsPrefix);
273             refElem.appendChild(transformsElem);
274             for (int i = 0, size = allTransforms.size(); i < size; i++) {
275                 DOMStructure transform =
276                     (DOMStructure) allTransforms.get(i);
277                 transform.marshal(transformsElem, dsPrefix, context);
278             }
279         }
280 
281         // create and append DigestMethod element
282         ((DOMDigestMethod) digestMethod).marshal(refElem, dsPrefix, context);
283 
284         // create and append DigestValue element
285         if (log.isLoggable(Level.FINE)) {
286             log.log(Level.FINE, "Adding digestValueElem");
287         }
288         Element digestValueElem = DOMUtils.createElement
289             (ownerDoc, "DigestValue", XMLSignature.XMLNS, dsPrefix);
290         if (digestValue != null) {
291             digestValueElem.appendChild
292                 (ownerDoc.createTextNode(Base64.encode(digestValue)));
293         }
294         refElem.appendChild(digestValueElem);
295 
296         parent.appendChild(refElem);
297         here = refElem.getAttributeNodeNS(null, "URI");
298     }
299 
300     public void digest(XMLSignContext signContext)
301         throws XMLSignatureException {
302         Data data = null;
303         if (appliedTransformData == null) {
304             data = dereference(signContext);
305         } else {
306             data = appliedTransformData;
307         }
308         digestValue = transform(data, signContext);
309 
310         // insert digestValue into DigestValue element
311         String encodedDV = Base64.encode(digestValue);
312         if (log.isLoggable(Level.FINE)) {
313             log.log(Level.FINE, "Reference object uri = " + uri);
314         }
315         Element digestElem = DOMUtils.getLastChildElement(refElem);
316         if (digestElem == null) {
317             throw new XMLSignatureException("DigestValue element expected");
318         }
319         DOMUtils.removeAllChildren(digestElem);
320         digestElem.appendChild
321             (refElem.getOwnerDocument().createTextNode(encodedDV));
322 
323         digested = true;
324         if (log.isLoggable(Level.FINE)) {
325             log.log(Level.FINE, "Reference digesting completed");
326         }
327     }
328 
329     public boolean validate(XMLValidateContext validateContext)
330         throws XMLSignatureException {
331         if (validateContext == null) {
332             throw new NullPointerException("validateContext cannot be null");
333         }
334         if (validated) {
335             return validationStatus;
336         }
337         Data data = dereference(validateContext);
338         calcDigestValue = transform(data, validateContext);
339 
340         if (log.isLoggable(Level.FINE)) {
341             log.log(Level.FINE, "Expected digest: "
342                 + Base64.encode(digestValue));
343             log.log(Level.FINE, "Actual digest: "
344                 + Base64.encode(calcDigestValue));
345         }
346 
347         validationStatus = Arrays.equals(digestValue, calcDigestValue);
348         validated = true;
349         return validationStatus;
350     }
351 
352     public Data getDereferencedData() {
353         return derefData;
354     }
355 
356     public InputStream getDigestInputStream() {
357         return dis;
358     }
359 
360     private Data dereference(XMLCryptoContext context)
361         throws XMLSignatureException {
362         Data data = null;
363 
364         // use user-specified URIDereferencer if specified; otherwise use deflt
365         URIDereferencer deref = context.getURIDereferencer();
366         if (deref == null) {
367             deref = DOMURIDereferencer.INSTANCE;
368         }
369         try {
370             data = deref.dereference(this, context);
371             if (log.isLoggable(Level.FINE)) {
372                 log.log(Level.FINE, "URIDereferencer class name: "
373                     + deref.getClass().getName());
374                 log.log(Level.FINE, "Data class name: "
375                     + data.getClass().getName());
376             }
377         } catch (URIReferenceException ure) {
378             throw new XMLSignatureException(ure);
379         }
380 
381         return data;
382     }
383 
384     private byte[] transform(Data dereferencedData,
385         XMLCryptoContext context) throws XMLSignatureException {
386 
387         if (md == null) {
388             try {
389                 md = MessageDigest.getInstance
390                     (((DOMDigestMethod) digestMethod).getMessageDigestAlgorithm());
391             } catch (NoSuchAlgorithmException nsae) {
392                 throw new XMLSignatureException(nsae);
393             }
394         }
395         md.reset();
396         DigesterOutputStream dos;
397         Boolean cache = (Boolean)
398             context.getProperty("javax.xml.crypto.dsig.cacheReference");
399         if (cache != null && cache.booleanValue() == true) {
400             this.derefData = copyDerefData(dereferencedData);
401             dos = new DigesterOutputStream(md, true);
402         } else {
403             dos = new DigesterOutputStream(md);
404         }
405         OutputStream os = new UnsyncBufferedOutputStream(dos);
406         Data data = dereferencedData;
407         for (int i = 0, size = transforms.size(); i < size; i++) {
408             DOMTransform transform = (DOMTransform) transforms.get(i);
409             try {
410                 if (i < size - 1) {
411                     data = transform.transform(data, context);
412                 } else {
413                     data = transform.transform(data, context, os);
414                 }
415             } catch (TransformException te) {
416                 throw new XMLSignatureException(te);
417             }
418         }
419 
420         try {
421             if (data != null) {
422                 XMLSignatureInput xi;
423                 // explicitly use C14N 1.1 when generating signature
424                 // first check system property, then context property
425                 boolean c14n11 = useC14N11;
426                 String c14nalg = CanonicalizationMethod.INCLUSIVE;
427                 if (context instanceof XMLSignContext) {
428                     if (!c14n11) {
429                         Boolean prop = (Boolean) context.getProperty
430                             ("com.sun.org.apache.xml.internal.security.useC14N11");
431                         c14n11 = (prop != null && prop.booleanValue() == true);
432                         if (c14n11) {
433                             c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
434                         }
435                     } else {
436                         c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
437                     }
438                 }
439                 if (data instanceof ApacheData) {
440                     xi = ((ApacheData) data).getXMLSignatureInput();
441                 } else if (data instanceof OctetStreamData) {
442                     xi = new XMLSignatureInput
443                         (((OctetStreamData)data).getOctetStream());
444                 } else if (data instanceof NodeSetData) {
445                     TransformService spi = null;
446                     try {
447                         spi = TransformService.getInstance(c14nalg, "DOM");
448                     } catch (NoSuchAlgorithmException nsae) {
449                         spi = TransformService.getInstance
450                             (c14nalg, "DOM", provider);
451                     }
452                     data = spi.transform(data, context);
453                     xi = new XMLSignatureInput
454                         (((OctetStreamData)data).getOctetStream());
455                 } else {
456                     throw new XMLSignatureException("unrecognized Data type");
457                 }
458                 if (context instanceof XMLSignContext && c14n11
459                     && !xi.isOctetStream() && !xi.isOutputStreamSet()) {
460                     DOMTransform t = new DOMTransform
461                         (TransformService.getInstance(c14nalg, "DOM"));
462                     Element transformsElem = null;
463                     String dsPrefix = DOMUtils.getSignaturePrefix(context);
464                     if (allTransforms.isEmpty()) {
465                         transformsElem = DOMUtils.createElement(
466                             refElem.getOwnerDocument(),
467                             "Transforms", XMLSignature.XMLNS, dsPrefix);
468                         refElem.insertBefore(transformsElem,
469                             DOMUtils.getFirstChildElement(refElem));
470                     } else {
471                         transformsElem = DOMUtils.getFirstChildElement(refElem);
472                     }
473                     t.marshal(transformsElem, dsPrefix, (DOMCryptoContext) context);
474                     allTransforms.add(t);
475                     xi.updateOutputStream(os, true);
476                 } else {
477                     xi.updateOutputStream(os);
478                 }
479             }
480             os.flush();
481             if (cache != null && cache.booleanValue() == true) {
482                 this.dis = dos.getInputStream();
483             }
484             return dos.getDigestValue();
485         } catch (Exception e) {
486             throw new XMLSignatureException(e);
487         }
488     }
489 
490     public Node getHere() {
491         return here;
492     }
493 
494     public boolean equals(Object o) {
495         if (this == o) {
496             return true;
497         }
498 
499         if (!(o instanceof Reference)) {
500             return false;
501         }
502         Reference oref = (Reference) o;
503 
504         boolean idsEqual = (id == null ? oref.getId() == null :
505             id.equals(oref.getId()));
506         boolean urisEqual = (uri == null ? oref.getURI() == null :
507             uri.equals(oref.getURI()));
508         boolean typesEqual = (type == null ? oref.getType() == null :
509             type.equals(oref.getType()));
510         boolean digestValuesEqual =
511             Arrays.equals(digestValue, oref.getDigestValue());
512 
513         return (digestMethod.equals(oref.getDigestMethod()) && idsEqual &&
514             urisEqual && typesEqual && allTransforms.equals(oref.getTransforms()));
515     }
516 
517     boolean isDigested() {
518         return digested;
519     }
520 
521     private static Data copyDerefData(Data dereferencedData) {
522         if (dereferencedData instanceof ApacheData) {
523             // need to make a copy of the Data
524             ApacheData ad = (ApacheData) dereferencedData;
525             XMLSignatureInput xsi = ad.getXMLSignatureInput();
526             if (xsi.isNodeSet()) {
527                 try {
528                     final Set s = xsi.getNodeSet();
529                     return new NodeSetData() {
530                         public Iterator iterator() { return s.iterator(); }
531                     };
532                 } catch (Exception e) {
533                     // log a warning
534                             log.log(Level.WARNING,
535                         "cannot cache dereferenced data: " + e);
536                     return null;
537                 }
538             } else if (xsi.isElement()) {
539                 return new DOMSubTreeData
540                     (xsi.getSubNode(), xsi.isExcludeComments());
541             } else if (xsi.isOctetStream() || xsi.isByteArray()) {
542                 try {
543                 return new OctetStreamData
544                   (xsi.getOctetStream(), xsi.getSourceURI(), xsi.getMIMEType());
545                 } catch (IOException ioe) {
546                     // log a warning
547                             log.log(Level.WARNING,
548                         "cannot cache dereferenced data: " + ioe);
549                     return null;
550                 }
551             }
552         }
553         return dereferencedData;
554     }
555 }